package com.zusmart.core.bean.support;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.zusmart.base.activity.support.AbstractActivity;
import com.zusmart.base.logging.Logger;
import com.zusmart.base.logging.LoggerFactory;
import com.zusmart.base.util.Assert;
import com.zusmart.base.util.StringUtils;
import com.zusmart.core.bean.BeanDefinition;
import com.zusmart.core.bean.BeanDefinitionRegistry;
import com.zusmart.core.bean.BeanDefinitionResolver;
import com.zusmart.core.bean.cause.BeanDefinitionMultipleException;
import com.zusmart.core.bean.cause.BeanDefinitionNotFoundException;
import com.zusmart.core.condition.ConditionManager;
import com.zusmart.core.condition.support.DefaultConditionManager;
import com.zusmart.core.config.ConfigSourceManager;
import com.zusmart.core.config.support.DefaultConfigSourceManager;
import com.zusmart.core.context.ApplicationContext;
import com.zusmart.core.plugin.PluginManager;
import com.zusmart.core.plugin.support.DefaultPluginManager;
import com.zusmart.core.proxy.ProxyManager;
import com.zusmart.core.proxy.support.cglib.CglibProxyManager;

public abstract class AbstractBeanDefinitionRegistry extends AbstractActivity<BeanDefinitionRegistry> implements BeanDefinitionRegistry {

	private static final Logger logger = LoggerFactory.getLogger(AbstractBeanDefinitionRegistry.class);

	private final Map<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<String, BeanDefinition>();
	private final Set<BeanDefinition> beanDefinitionSorted = new LinkedHashSet<BeanDefinition>();

	private final ApplicationContext applicationContext;
	private final BeanDefinitionResolver beanDefinitionResolver;
	private final ConfigSourceManager configSourceManager;
	private final ConditionManager conditionManager;
	private final PluginManager pluginManager;
	private final ProxyManager proxyManager;

	protected AbstractBeanDefinitionRegistry(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
		this.beanDefinitionResolver = this.createBeanDefinitionResolver();
		this.configSourceManager = this.createConfigSourceManager();
		this.conditionManager = this.createConditionManager();
		this.pluginManager = this.createPluginManager();
		this.proxyManager = this.createProxyManager();
	}

	@Override
	public BeanDefinition attachBeanDefinition(BeanDefinition beanDefinition) throws BeanDefinitionMultipleException {
		if (null == beanDefinition) {
			logger.warn("bean definition is null");
			return beanDefinition;
		}
		if (this.existsBeanDefinition(beanDefinition.getBeanName())) {
			throw new BeanDefinitionMultipleException(beanDefinition.toString());
		}
		this.beanDefinitions.put(beanDefinition.getBeanName(), beanDefinition);
		return beanDefinition;
	}

	@Override
	public BeanDefinition attachBeanDefinition(Class<?> beanClass, String beanName, String beanScope) throws BeanDefinitionMultipleException {
		Assert.isNull(beanClass, "bean class must not be null");
		if (StringUtils.isBlank(beanName)) {
			beanName = this.beanDefinitionResolver.getBeanName(beanClass);
		}
		if (StringUtils.isBlank(beanScope)) {
			beanScope = this.beanDefinitionResolver.getBeanScope(beanClass);
		}
		BeanDefinition beanDefinition = new BeanDefinitionForClass(this.beanDefinitionResolver, this, beanName, beanScope, beanClass);
		this.attachBeanDefinition(beanDefinition);
		return beanDefinition;
	}

	@Override
	public BeanDefinition attachBeanDefinition(Class<?> beanClass, String beanName) throws BeanDefinitionMultipleException {
		return this.attachBeanDefinition(beanClass, beanName, null);
	}

	@Override
	public BeanDefinition attachBeanDefinition(Class<?> beanClass) throws BeanDefinitionMultipleException {
		return this.attachBeanDefinition(beanClass, null, null);
	}

	@Override
	public BeanDefinition attachBeanDefinition(Object targetBeanInstance) throws BeanDefinitionMultipleException {
		return this.attachBeanDefinition(targetBeanInstance, null);
	}

	@Override
	public BeanDefinition attachBeanDefinition(Object targetBeanInstance, String beanName) throws BeanDefinitionMultipleException {
		Assert.isNull(targetBeanInstance, "bean instance must not be null");
		if (StringUtils.isBlank(beanName)) {
			beanName = this.beanDefinitionResolver.getBeanName(targetBeanInstance);
		}
		BeanDefinition beanDefinition = new BeanDefinitionForObject(this.beanDefinitionResolver, this, targetBeanInstance, beanName);
		this.attachBeanDefinition(beanDefinition);
		return beanDefinition;
	}

	@Override
	public BeanDefinition attachBeanDefinition(BeanDefinition parentBeanDefinition, Method parentBeanMethod, String beanName, String beanScope) throws BeanDefinitionMultipleException {
		Assert.isNull(parentBeanDefinition, "parent bean definition must not be null");
		Assert.isNull(parentBeanMethod, "parent bean method must not be null");
		if (StringUtils.isBlank(beanName)) {
			beanName = this.beanDefinitionResolver.getBeanName(parentBeanMethod);
		}
		if (StringUtils.isBlank(beanScope)) {
			beanScope = this.beanDefinitionResolver.getBeanScope(parentBeanMethod);
		}
		BeanDefinition beanDefinition = new BeanDefinitionForMethod(this.beanDefinitionResolver, this, parentBeanDefinition, parentBeanMethod, beanName, beanScope);
		this.attachBeanDefinition(beanDefinition);
		return beanDefinition;
	}

	@Override
	public BeanDefinition attachBeanDefinition(BeanDefinition parentBeanDefinition, Method parentBeanMethod, String beanName) throws BeanDefinitionMultipleException {
		return this.attachBeanDefinition(parentBeanDefinition, parentBeanMethod, beanName, null);
	}

	@Override
	public BeanDefinition attachBeanDefinition(BeanDefinition parentBeanDefinition, Method parentBeanMethod) throws BeanDefinitionMultipleException {
		return this.attachBeanDefinition(parentBeanDefinition, parentBeanMethod, null, null);
	}

	@Override
	public BeanDefinition detachBeanDefinition(String beanName) throws BeanDefinitionNotFoundException {
		if (StringUtils.isBlank(beanName)) {
			logger.warn("bean name is blank");
			return null;
		}
		if (!this.existsBeanDefinition(beanName)) {
			throw new BeanDefinitionNotFoundException(beanName);
		}
		return this.beanDefinitions.remove(beanName);
	}

	@Override
	public BeanDefinition detachBeanDefinition(BeanDefinition beanDefinition) throws BeanDefinitionNotFoundException {
		if (null == beanDefinition) {
			logger.warn("bean definition is null");
			return null;
		}
		return this.detachBeanDefinition(beanDefinition.getBeanName());
	}

	@Override
	public Set<String> getBeanDefinitionNames() {
		return Collections.unmodifiableSet(this.beanDefinitions.keySet());
	}

	@Override
	public boolean existsBeanDefinition(String beanName) {
		if (StringUtils.isBlank(beanName)) {
			return false;
		}
		return this.beanDefinitions.containsKey(beanName);
	}

	@Override
	public boolean existsBeanDefinition(Class<?> beanType) {
		if (null == beanType) {
			return false;
		}
		for (Entry<String, BeanDefinition> entry : this.beanDefinitions.entrySet()) {
			if (entry.getValue().isAssignable(beanType)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public Map<String, BeanDefinition> getBeanDefinitionMappings() {
		return Collections.unmodifiableMap(this.beanDefinitions);
	}

	@Override
	public BeanDefinition getBeanDefinition(String beanName) throws BeanDefinitionNotFoundException {
		if (StringUtils.isNotBlank(beanName) && this.existsBeanDefinition(beanName)) {
			return this.beanDefinitions.get(beanName);
		}
		throw new BeanDefinitionNotFoundException(beanName);
	}

	@Override
	public BeanDefinition getBeanDefinition(Class<?> beanClass) throws BeanDefinitionNotFoundException, BeanDefinitionMultipleException {
		if (null != beanClass) {
			BeanDefinition beanDefinition = null;
			for (Entry<String, BeanDefinition> entry : this.beanDefinitions.entrySet()) {
				if (entry.getValue().isAssignable(beanClass)) {
					if (null == beanDefinition) {
						beanDefinition = entry.getValue();
					} else {
						throw new BeanDefinitionMultipleException(beanClass.getName());
					}
				}
			}
			if (null == beanDefinition) {
				throw new BeanDefinitionNotFoundException(beanClass.getName());
			}
			return beanDefinition;
		}
		throw new BeanDefinitionNotFoundException();
	}

	@Override
	public BeanDefinition getBeanDefinition(String beanName, Class<?> beanClass) throws BeanDefinitionNotFoundException, BeanDefinitionMultipleException {
		if (StringUtils.isBlank(beanName) && null == beanClass) {
			throw new BeanDefinitionNotFoundException();
		}
		if (StringUtils.isBlank(beanName) && null != beanClass) {
			return this.getBeanDefinition(beanClass);
		} else if (StringUtils.isNotBlank(beanName) && null == beanClass) {
			return this.getBeanDefinition(beanName);
		} else {
			Set<BeanDefinition> beanDefinitions = this.getBeanDefinitions(beanClass);
			if (null == beanDefinitions || beanDefinitions.size() == 0) {
				throw new BeanDefinitionNotFoundException(beanClass.getName());
			}
			for (BeanDefinition bean : beanDefinitions) {
				if (bean.getBeanName().equals(beanName)) {
					return bean;
				}
			}
			throw new BeanDefinitionNotFoundException(String.format("bean type (%s) , bean name (%s)", beanClass.getName(), beanName));
		}
	}

	@Override
	public Set<BeanDefinition> getBeanDefinitions(Class<?> beanClass) throws BeanDefinitionNotFoundException {
		if (null != beanClass) {
			Set<BeanDefinition> result = new LinkedHashSet<BeanDefinition>();
			for (Entry<String, BeanDefinition> entry : this.beanDefinitions.entrySet()) {
				if (entry.getValue().isAssignable(beanClass)) {
					result.add(entry.getValue());
				}
			}
			if (result.size() == 0) {
				throw new BeanDefinitionNotFoundException(beanClass.getName());
			}
			return result;
		}
		throw new BeanDefinitionNotFoundException();
	}

	@Override
	protected BeanDefinitionRegistry getActivity() {
		return this;
	}
	
	@Override
	public ApplicationContext getApplicationContext() {
		return this.applicationContext;
	}
	
	@Override
	public BeanDefinitionResolver getBeanDefinitionResolver() {
		return this.beanDefinitionResolver;
	}

	@Override
	public ConfigSourceManager getConfigSourceManager() {
		return this.configSourceManager;
	}

	@Override
	public ConditionManager getConditionManager() {
		return this.conditionManager;
	}

	@Override
	public PluginManager getPluginManager() {
		return this.pluginManager;
	}

	@Override
	public ProxyManager getProxyManager() {
		return this.proxyManager;
	}
	
	@Override
	protected void doStart() throws Exception {
		this.pluginManager.start();
		this.initInternalBeanDefinition();
		this.beanDefinitionSorted.clear();
		this.beanDefinitionSorted.addAll(this.getBeanDefinitionResolver().getSortedBeanDefinitions());
		this.refreshBeanDefinitions(this.beanDefinitionSorted);
		if (this.beanDefinitionSorted.size() > 0) {
			for (BeanDefinition beanDefinition : this.beanDefinitionSorted) {
				beanDefinition.doCreate();
			}
			for (BeanDefinition beanDefinition : this.beanDefinitionSorted) {
				beanDefinition.doInject();
			}
			for (BeanDefinition beanDefinition : this.beanDefinitionSorted) {
				beanDefinition.doInitial();
				logger.debug("bean definition initial -> {}", beanDefinition);
			}
		}
	}

	@Override
	protected void doClose() throws Exception {
		if (this.beanDefinitionSorted.size() > 0) {
			for (BeanDefinition beanDefinition : this.beanDefinitionSorted) {
				beanDefinition.doRelease();
			}
			for (BeanDefinition beanDefinition : this.beanDefinitionSorted) {
				beanDefinition.doDestory();
				logger.debug("bean definition destory -> {}", beanDefinition);
			}
			this.beanDefinitionSorted.clear();
		}
		this.pluginManager.close();
	}

	protected abstract BeanDefinitionResolver createBeanDefinitionResolver();

	protected ConfigSourceManager createConfigSourceManager() {
		return new DefaultConfigSourceManager();
	}

	protected ConditionManager createConditionManager() {
		return new DefaultConditionManager(this);
	}

	protected PluginManager createPluginManager() {
		return new DefaultPluginManager(this);
	}

	protected ProxyManager createProxyManager() {
		return new CglibProxyManager();
	}

	protected void initInternalBeanDefinition() throws BeanDefinitionMultipleException {
		this.attachBeanDefinition(this);
		this.attachBeanDefinition(this.configSourceManager);
		this.attachBeanDefinition(this.pluginManager);
	}

	protected void refreshBeanDefinitions(Set<BeanDefinition> beanDefinitions) {
		this.beanDefinitions.clear();
		for (BeanDefinition beanDefinition : beanDefinitions) {
			this.beanDefinitions.put(beanDefinition.getBeanName(), beanDefinition);
		}
	}

}